#ifndef MEASURE_SIZE_TRACKER_POINT_2_H_
#define MEASURE_SIZE_TRACKER_POINT_2_H_

#if defined(_MSC_VER)
#pragma once
#endif

#include "vector_2.h"
#include "common_definitions.h"
#include "geometries_forward.h"
#include <boost/serialization/nvp.hpp>
#pragma warning(push)  
#pragma warning(disable:4244)
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#pragma warning(pop)

class point_2 {
  friend class boost::serialization::access;

public:
  enum MouseHit
  {
    kHitNothing = kMouseHitNothing,
    kHitMiddle  = kMouseHitOn,

    kHitButt
  };

  enum KeyStroke
  {
    kStrokeNothing    = kKeyStrokeNothing,

    kStrokeLineUp     = 0,
    kStrokeLineDown   = 1,
    kStrokeLineLeft   = 2,
    kStrokeLineRight  = 3,

    kStrokePageUp     = 4,
    kStrokePageDown   = 5,
    kStrokePageLeft   = 6,
    kStrokePageRight  = 7,

    kStrokeButt
  };

public:
  point_2(void) : x(0), y(0) {}
  point_2(double xval, double yval) : x(xval), y(yval) {}
  ~point_2(void) {}

  point_2(const point_2& right_value)
  { 
    if (this != &right_value) {
      x = right_value.x;
      y = right_value.y;
    }
  }

  point_2& operator=(const point_2& right_value)
  {
    if (this != &right_value) {
      x = right_value.x;
      y = right_value.y;
    }

    return *this;
  }

  bool operator==(const point_2& right_operand) const
  {
    return (x==right_operand.x && y==right_operand.y);
  }

  bool operator!=(const point_2& right_operand) const
  {
    return !(*this==right_operand);
  }

public:
  point_2 operator+(const vector_2& rvt) const 
  { 
    return point_2(x+rvt.vx, y-rvt.vy); 
  }

  point_2 operator-(const vector_2& rvt) const
  { 
    return point_2(x-rvt.vx, y+rvt.vy);
  }

  point_2& operator+=(const vector_2& rvt)
  { 
    x = x+rvt.vx;
    y = y-rvt.vy;

    return *this;
  }

  point_2& operator-=(const vector_2& rvt)
  { 
    x = x-rvt.vx;
    y = y+rvt.vy;

    return *this;
  }

  friend point_2 operator+(const point_2& lpt, const point_2& rpt)
  { 
    return point_2(lpt.x+rpt.x, lpt.y+rpt.y); 
  }

  friend vector_2 operator-(const point_2& lpt, const point_2& rpt)
  { 
    return vector_2(lpt.x-rpt.x, rpt.y-lpt.y); 
  }

  friend point_2 operator*(const point_2& lpt, double rs)
  { 
    return point_2(lpt.x*rs, lpt.y*rs); 
  }

  friend point_2 operator*(double ls, const point_2& rpt)
  { 
    return point_2(rpt.x*ls, rpt.y*ls); 
  }

  friend point_2 operator/(const point_2& lpt, double rs)
  { 
    return point_2(lpt.x/rs, lpt.y/rs); 
  }

public:
  double distance(const point_2& pt) const
  {
    double dx = pt.x - x;
    double dy = pt.y - y;

    return sqrt(dx*dx + dy*dy);
  }

  double distance2(const point_2& pt) const
  {
    double dx = pt.x - x;
    double dy = pt.y - y;

    return dx*dx + dy*dy;
  }

  double distance(const point_2& pt0, const point_2& pt1, double len) const;

public:
  point_2 rotate(const point_2& axis, int a) const
  {
    vector_2 vec2 = *this - axis;
    return axis + vec2.rotate(a);
  }

  point_2 rotate(int a) const { return rotate(point_2(0, 0), a); }
  point_2 interpolate(const point_2& ep, double t) const;

public:
  int lx(void) const { return boost::math::lround(x); }
  int ly(void) const { return boost::math::lround(y); }

public:
  bool left(const point_2& start, const point_2& end) const
  { 
    return (end.x-start.x)*(y-start.y)-(x-start.x)*(end.y-start.y)>0; 
  }

  bool right(const point_2& start, const point_2& end) const
  { 
    return (end.x-start.x)*(y-start.y)-(x-start.x)*(end.y-start.y)<0; 
  }

  bool acute(const point_2& point0, const point_2& point1) const
  {
    return (point0-(*this)).acute(point1-(*this));
  }

  bool adjacent(const point_2& pt, int tolerance) const
  {
    return distance2(pt) < tolerance*tolerance;
  }

  bool intersects(const box_2& box) const;

public:
  double x;
  double y;

private:
  template<class Archive>
  void serialize(Archive &ar, const unsigned int version)
  {
    using boost::serialization::make_nvp;
    ar & BOOST_SERIALIZATION_NVP(x);
    ar & BOOST_SERIALIZATION_NVP(y);
  }
};

BOOST_GEOMETRY_REGISTER_POINT_2D(point_2, double, boost::geometry::cs::cartesian, x, y)

#endif // MEASURE_SIZE_TRACKER_POINT_2_H_